package org.xm.sk.service.data.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xm.sk.dao.BoderRepository;
import org.xm.sk.vo.e.BoderStatus;
import org.xm.sk.vo.e.Datalevel;
import org.xm.sk.vo.ob.IncludeQuery;
import org.xm.sk.vo.ob.PageConf;
import org.xm.sk.vo.ob.Query;
import org.xm.sk.vo.tb.Boder;
import org.xm.sk.vo.tb.QBoder;
import org.xm.sk.vo.tb.User;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;

@Service
@Transactional
public class BoderServiceImpl extends BaseServiceImpl<Boder> {

    private static final String CACHE_KEY = "'boder'";
    private static final String sk_CACHE_NAME = "boders";

    // 实体管理者
    @Autowired
    private EntityManager entityManager;

    // JPA查询工厂
    private JPAQueryFactory queryFactory;

    @Autowired
    private BoderRepository boderRepository;

    @PostConstruct
    public void initFactory() {
        queryFactory = new JPAQueryFactory(entityManager);
    }

    @Cacheable(value = sk_CACHE_NAME, key = CACHE_KEY + "'_'+#p0")
    public Boder findById(int id) {
        return this.boderRepository.getOne(id);
    }

    @Cacheable(value = sk_CACHE_NAME, key = CACHE_KEY + "'_'+#p0")
    public Set<Boder> findByName(String name) {
        return this.boderRepository.findByName(name);
    }

    @Cacheable(value = sk_CACHE_NAME, key = CACHE_KEY + "'_all'")
    public List<Boder> findAll() {
        return this.boderRepository.findAll();
    }

    @Cacheable(value = sk_CACHE_NAME, key = CACHE_KEY + "'_'+#p0")
    public List<Boder> findAll(Boder boder) {
        ExampleMatcher matcher = ExampleMatcher.matching();
        Example<Boder> ex = Example.of(boder, matcher);
        return this.boderRepository.findAll(ex);
    }

    @Cacheable(value = sk_CACHE_NAME, key = CACHE_KEY + "'_'+#p0")
    public Object[] findAllByPage(PageConf pageConf) {
        List<Order> orders = new ArrayList<Order>();
        if (null != pageConf.getSortNames()) {
            for (int i = 0; i < pageConf.getSortNames().size(); i++) {
                orders.add(new Order(pageConf.getSortOrders().get(i),
                        pageConf.getSortNames().get(i)));
            }
        } else {
            orders.add(new Order(Direction.DESC, "id"));
        }
        Sort sort = Sort.by(orders);
        Pageable pageable = null;
        Specification<Boder> querySpecifi = new Specification<Boder>() {

            private static final long serialVersionUID = 1044391210747920349L;

            @Override
            public Predicate toPredicate(Root<Boder> root,
                    CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                Predicate predicate = cb.and(
                        cb.equal(root.get("datalevel").as(Integer.class), 0),
                        cb.greaterThanOrEqualTo(
                                root.get("status").as(Integer.class), 1));
                List<Predicate> predicates = new ArrayList<>();
                if (null != pageConf.getQuery()) {
                    Query q = pageConf.getQuery();
                    if (null != q.getIntTime() && 1 < q.getIntTime().length) {
                        predicates.add(cb.between(
                                root.get("mktime").as(Date.class),
                                q.getIntTime()[0], q.getIntTime()[1]));
                    }
                    if (null != q.getModifydate()
                            && 0 < q.getModifydate().trim().length()) {
                        predicates.add(
                                cb.like(root.get("modifydate").as(String.class),
                                        "%" + q.getModifydate() + "%"));
                    }
                    if (null != q.getItemNames()
                            && 0 < q.getItemNames().trim().length()) {
                        predicates.add(
                                cb.like(root.get("itemNames").as(String.class),
                                        "%" + q.getItemNames() + "%"));
                    }
                    if (null != q.getHairdresserName()
                            && 0 < q.getHairdresserName().trim().length()) {
                        String tp = q.getHairdresserName();
                        if (-1 < tp.indexOf(":"))
                            tp = tp.substring(0, tp.indexOf(":"));
                        predicates.add(cb.like(
                                root.get("hairdresserName").as(String.class),
                                "%" + tp + "%"));
                    }
                    if (null != q.getUserName()
                            && 0 < q.getUserName().trim().length()) {
                        String tp = q.getUserName();
                        if (-1 < tp.indexOf(":"))
                            tp = tp.substring(0, tp.indexOf(":"));
                        Join<Boder, User> joinUser = root.join("user",
                                JoinType.LEFT);
                        predicates.add(cb.or(
                                cb.like(joinUser.get("name").as(String.class),
                                        "%" + tp + "%"),
                                cb.like(joinUser.get("nickname")
                                        .as(String.class), "%" + tp + "%")));
                    }
                    if (null != q.getShopName()
                            && 0 < q.getShopName().trim().length()) {
                        predicates.add(
                                cb.like(root.get("shopName").as(String.class),
                                        "%" + q.getShopName() + "%"));
                    }
                    if (null != q.getBoderStatus()) {
                        if (!q.getBoderStatus().equals(BoderStatus.SERVERING)) {
                            predicates.add(cb.greaterThan(
                                    root.get("status").as(BoderStatus.class),
                                    BoderStatus.SERVERING));
                        } else {
                            predicates.add(cb.equal(
                                    root.get("status").as(BoderStatus.class),
                                    q.getBoderStatus()));
                        }
                    }
                    return cb.and(
                            cb.and(predicates
                                    .toArray(new Predicate[predicates.size()])),
                            predicate);
                } else if (null != pageConf.getSearch()
                        && 0 < pageConf.getSearch().trim().length()) {
                    String search = pageConf.getSearch().trim();
                    // 模糊查找
                    predicates.add(cb.like(root.get("hairdresserName").as(String.class),
                            "%" + search + "%"));
                    predicates.add(cb.like(root.get("shopName").as(String.class),
                            "%" + search + "%"));
                    predicates.add(cb.like(root.get("itemNames").as(String.class),
                            "%" + search + "%"));
                    predicates.add(cb.like(root.get("couponnum").as(String.class),
                            "%" + search + "%"));
                    predicates.add(cb.like(root.get("phone").as(String.class),
                            "%" + search + "%"));
                    predicates.add(
                            cb.like(root.get("modifydate").as(String.class),
                                    "%" + search + "%"));
                    return cb.and(
                            cb.or(predicates
                                    .toArray(new Predicate[predicates.size()])),
                            predicate);
                }
                return predicate;
            }
        };
        if (null != pageConf.getPage() && null != pageConf.getSize())
            pageable = PageRequest.of(pageConf.getPage(), pageConf.getSize(),
                    sort);
        QBoder _QBoder = QBoder.boder;
        BooleanExpression predicate = _QBoder.datalevel.eq(Datalevel.NORMAL);
        predicate = predicate.and(_QBoder.status.goe(BoderStatus.SERVERING));
        if (null != pageConf.getQuery()) {
            Query q = pageConf.getQuery();
            if (null != q.getIntTime() && 1 < q.getIntTime().length) {
                predicate = predicate.and(_QBoder.mktime
                        .between(q.getIntTime()[0], q.getIntTime()[1]));
            }
            if (null != q.getModifydate()) {
                predicate = predicate.and(_QBoder.modifydate.stringValue()
                        .like("%" + q.getModifydate() + "%"));
            }
            if (null != q.getItemNames()) {
                predicate = predicate.and(
                        _QBoder.itemNames.like("%" + q.getItemNames() + "%"));
            }
            if (null != q.getHairdresserName()) {
                String tp = q.getHairdresserName();
                if (-1 < tp.indexOf(":"))
                    tp = tp.substring(0, tp.indexOf(":"));
                predicate = predicate.and(_QBoder.hairdresserName
                        .like("%" + tp + "%"));
            }
            if (null != q.getUserName()) {
                String tp = q.getUserName();
                if (-1 < tp.indexOf(":"))
                    tp = tp.substring(0, tp.indexOf(":"));
                predicate = predicate
                        .and(_QBoder.user.nickname.like("%" + tp + "%")
                                .or(_QBoder.user.name.like("%" + tp + "%")));
            }
            if (null != q.getShopName()) {
                predicate = predicate.and(
                        _QBoder.shopName.like("%" + q.getShopName() + "%"));
            }
            if (null != q.getBoderStatus()) {
                if (!q.getBoderStatus().equals(BoderStatus.SERVERING)) {
                    predicate = predicate
                            .and(_QBoder.status.gt(BoderStatus.SERVERING));
                } else {
                    predicate = predicate
                            .and(_QBoder.status.eq(q.getBoderStatus()));
                }
            }
        } else if (null != pageConf.getSearch()
                && 0 < pageConf.getSearch().trim().length()) {
            String search = pageConf.getSearch().trim();
            predicate = predicate.and(_QBoder.hairdresserName.like("%" + search + "%")
                    .or(_QBoder.shopName.like("%" + search + "%")
                            .or(_QBoder.itemNames.like("%" + search + "%")
                                    .or(_QBoder.couponnum.like("%" + search + "%")
                                            .or(_QBoder.phone.like("%" + search + "%")
                                                    .or(_QBoder.modifydate.stringValue().like("%" + search + "%")))))));
        }
        Tuple tuple = queryFactory
                .select(_QBoder.priceValue.sum(), _QBoder.brokerage.sum(), _QBoder.pay.sum(), _QBoder.couponValue.sum())
                .from(_QBoder).where(predicate).fetchOne();
        if (null != pageable)
            return new Object[] {
                    this.boderRepository.findAll(querySpecifi, pageable),
                    tuple.get(_QBoder.priceValue.sum()),
                    tuple.get(_QBoder.brokerage.sum()),
                    tuple.get(_QBoder.pay.sum()),
                    tuple.get(_QBoder.couponValue.sum()) };
        else
            return new Object[] {
                    this.boderRepository.findAll(querySpecifi, sort),
                    tuple.get(_QBoder.priceValue.sum()),
                    tuple.get(_QBoder.brokerage.sum()),
                    tuple.get(_QBoder.pay.sum()),
                    tuple.get(_QBoder.couponValue.sum()) };
    }

    @Cacheable(value = sk_CACHE_NAME, key = CACHE_KEY + "'_'+#p0+'_'+#p1")
    public Object findAllByPage(PageConf pageConf, Object object) {
        if (null != object && object instanceof IncludeQuery) {
            IncludeQuery includeQuery = (IncludeQuery) object;
            Class<?> clazz = boderRepository.getClass();
            try {
                Method m = clazz.getMethod("findBy"
                        + captureName(includeQuery.getPropertyName()) + "In",
                        Set.class);
                return m.invoke(boderRepository,
                        includeQuery.getPropertyValues());
            } catch (NoSuchMethodException | SecurityException
                    | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {
                e.printStackTrace();
            }
        } else if (null != object && object instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) object;
            if (null != map.get("userid")) {
                Integer userid = Integer.valueOf(map.get("userid").toString());
                BoderStatus boderStatus = (BoderStatus) map.get("boderStatus");
                Pageable pageable = PageRequest.of(pageConf.getPage(),
                        pageConf.getSize());
                if (BoderStatus.SERVERING == boderStatus)
                    return this.boderRepository.listMakingByUserId(pageable,
                            userid, boderStatus);
                else
                    return this.boderRepository.listPaidByUserId(pageable,
                            userid, boderStatus);
            } else if (null != map.get("hairdresserid")) {
                Integer hairdresserid = Integer
                        .valueOf(map.get("hairdresserid").toString());
                if (null != map.get("boderStatus")) {
                    BoderStatus boderStatus = (BoderStatus) map
                            .get("boderStatus");
                    Pageable pageable = PageRequest.of(pageConf.getPage(),
                            pageConf.getSize());
                    if (BoderStatus.METE == boderStatus)
                        return this.boderRepository.listCmByHairdresserId(
                                pageable, hairdresserid, boderStatus);
                    else
                        return this.boderRepository.listUnByHairdresserId(
                                pageable, hairdresserid, boderStatus);
                } else if (null != map.get("tag")) {
                    String tag = (String) map.get("tag");
                    try {
                        DateFormat fmt = new SimpleDateFormat(
                                "yyyy-MM-dd HH:mm:ss");
                        if (tag.length() > 6) {
                            Date date = fmt.parse(tag + " 23:59:59");
                            return this.boderRepository
                                    .performanceByDay4Hairdresser(hairdresserid,
                                            date);
                        } else if (tag.length() > 4) {
                            Calendar cal = Calendar.getInstance();
                            cal.set(Calendar.YEAR, Integer.parseInt(
                                    tag.substring(0, tag.indexOf("-"))));
                            cal.set(Calendar.MONTH,
                                    Integer.parseInt(
                                            tag.substring(tag.indexOf("-") + 1))
                                            - 1);
                            Date date = fmt.parse(tag + "-"
                                    + cal.getActualMaximum(Calendar.DATE)
                                    + " 23:59:59");
                            return this.boderRepository
                                    .performanceByMonth4Hairdresser(
                                            hairdresserid, date);
                        } else {
                            Date date = fmt.parse(tag + "-12-31 23:59:59");
                            return this.boderRepository
                                    .performanceByYear4Hairdresser(
                                            hairdresserid, date);
                        }
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                } else if (null != map.get("queryServerTime")) {
                    return boderRepository.listServerTime(hairdresserid,
                            BoderStatus.SERVERING);
                } else if (null != map.get("mktime")) {
                    Date mktime = (Date) map.get("mktime");
                    return boderRepository.exBoder(hairdresserid, mktime,
                            BoderStatus.SERVERING);
                } else {
                    return boderRepository.waitCount4Hairdresser(hairdresserid,
                            BoderStatus.SERVERING);
                }
            } else
                return null;
        }
        return null;
    }

    @CacheEvict(value = sk_CACHE_NAME, allEntries = true)
    // @CachePut(value = sk_CACHE_NAME, key = CACHE_KEY + "'_'+#p0.id")
    public Boder update(Boder boder) {
        boder.setEditer(user().getUserid());
        boder.setEditerName(user().getUsername());
        boder.setModifydate(new Date());
        return this.boderRepository.save(boder);
    }

    @CacheEvict(value = sk_CACHE_NAME, allEntries = true)
    public void delete(int id) {
        this.boderRepository.delete(id, user().getUserid(),
                user().getUsername());
    }

    @CacheEvict(value = sk_CACHE_NAME, allEntries = true)
    public void delete(Set<Integer> ids) {
        this.boderRepository.delete(ids, user().getUserid(),
                user().getUsername());
    }

    @CacheEvict(value = sk_CACHE_NAME, allEntries = true)
    public Boder add(Boder boder) {
        boder.setCreator(user().getUserid());
        boder.setEditer(user().getUserid());
        boder.setCreatorName(user().getUsername());
        boder.setEditerName(user().getUsername());
        return this.boderRepository.saveAndFlush(boder);
    }

    protected static String captureName(String name) {
        name = name.substring(0, 1).toUpperCase() + name.substring(1);
        return name;
    }
}
